package app.caihan.scframe.common.loadinghelper

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.IdRes
import app.caihan.scframe.R
import app.caihan.scframe.constant.DEBUG_TAG
import com.blankj.utilcode.util.LogUtils
import org.jetbrains.anko.toast

/**
 * 来源:https://github.com/DylanCaiCoding/LoadingHelper
 * 史上耦合度最低的添加标题栏方式:https://juejin.im/post/6844904196982964232
 * @since v2.1.0
 * @author Seven-C
 * @date 2020/8/3
 */
open class LoadingHelper @JvmOverloads constructor(
    private val contentView: View,
    contentAdapter: ContentAdapter<*>? = null
) {
    lateinit var decorView: View
        private set
    private lateinit var contentParent: ViewGroup
    private val parent: ViewGroup?
    private var currentViewHolder: ViewHolder? = null
    private var onReloadListener: OnReloadListener? = null
    private var adapters: HashMap<Any, Adapter<*>> = HashMap()
    private val viewHolders: HashMap<Any, ViewHolder> = HashMap()

    companion object {
        private var adapterPool: (AdapterPool.() -> Unit)? = null

        @JvmStatic
        fun setDefaultAdapterPool(adapterPool: AdapterPool.() -> Unit) {
            Companion.adapterPool = adapterPool
        }
    }

    /**
     * Constructs a LoadingHelper with a activity and a content adapter
     *
     * @param activity       the activity
     * @param contentAdapter the adapter of creating content view
     */
    @JvmOverloads
    constructor(activity: Activity, contentAdapter: ContentAdapter<*>? = null) : this(
        (activity.findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0),
        contentAdapter
    )

    init {
        adapterPool?.let {
            AdapterPool(
                this
            ).apply(it)
        }
        parent = contentView.parent as ViewGroup?
        register(ViewType.CONTENT, contentAdapter ?: SimpleContentAdapter())
        setDecorAdapter(
            LinearDecorAdapter(
                listOf()
            )
        )
    }

    /**
     * Sets an adapter for decorating content view.
     *
     * @param decorAdapter the adapter for decorating content view.
     * @since v2.0.0
     */
    fun setDecorAdapter(decorAdapter: DecorAdapter) {
        currentViewHolder = null
        if (parent != null) {
            val index = parent.indexOfChild(contentView)
            if (index >= 0) {
                parent.removeView(contentView)
            } else {
                parent.removeView(decorView)
                (contentView.parent as ViewGroup).removeView(contentView)
            }
            decorView = decorAdapter.createDecorView()
            parent.addView(decorView, index)
        } else {
            decorView = decorAdapter.createDecorView()
        }
        contentParent = decorAdapter.getContentParent(decorView)
        showView(ViewType.CONTENT)
    }

    /**
     * Adds one or more views to decorate content in the header.
     *
     * @param viewType the view type of adapter
     * @since v2.0.0
     */
    fun setDecorHeader(viewType: ViewType) {
        val views = mutableListOf<View>()
        val viewHolder = getViewHolder(viewType)
        val rootView = viewHolder.rootView
        views.add(rootView)
        setDecorAdapter(LinearDecorAdapter(views))
    }

    /**
     * Adds child decorative view between the content and the decorative view.
     *
     * @param decorAdapter the adapter for decorating content view.
     * @since v2.1.0
     */
    fun addChildDecorAdapter(decorAdapter: DecorAdapter) {
        contentParent.removeView(currentViewHolder?.rootView)
        currentViewHolder = null
        val childDecorView = decorAdapter.createDecorView()
        contentParent.addView(childDecorView)
        contentParent = decorAdapter.getContentParent(childDecorView)
        showView(ViewType.CONTENT)
    }

    /**
     * Adds child decorative header between the content and the decorative view.
     *
     * @param viewTypes the view type of adapter
     * @since v2.1.0
     */
    fun addChildDecorHeader(vararg viewTypes: ViewType) {
        val views = mutableListOf<View>()
        for (viewType in viewTypes) {
            views.add(getViewHolder(viewType).rootView)
        }
        addChildDecorAdapter(
            LinearDecorAdapter(
                views
            )
        )
    }

    private fun DecorAdapter.createDecorView() =
        onCreateDecorView(LayoutInflater.from(contentView.context)).also { decorView ->
            if (contentView.layoutParams != null) {
                decorView.layoutParams = contentView.layoutParams
            }
        }

    /**
     * Registers the adapter of creating view before showing view.
     *
     * @param viewType the view type of adapter
     * @param adapter  the adapter of creating view
     */
    fun register(viewType: ViewType, adapter: Adapter<*>) {
        adapters[viewType] = adapter
    }

    /**
     * Called if you need to handle reload event, you can get the listener of reloading data from view holder.
     *
     * @param onReloadListener the listener of reloading data
     */
    fun setOnReloadListener(onReloadListener: OnReloadListener) {
        this.onReloadListener = onReloadListener
    }

    fun setOnReloadListener(onReload: () -> Unit) =
        setOnReloadListener(object :
            OnReloadListener {
            override fun onReload() = onReload()
        })

    fun showLoadingView() = showView(ViewType.LOADING)

    fun showContentView() = showView(ViewType.CONTENT)

    fun showErrorView() = showView(ViewType.ERROR)

    fun showEmptyView() = showView(ViewType.EMPTY)

    /**
     * Shows the view by view type
     *
     * @param viewType the view type of adapter
     */
    private fun showView(viewType: ViewType) {
        if (currentViewHolder == null) {
            addView(viewType)
        } else {
            if (viewType !== currentViewHolder!!.viewType && currentViewHolder!!.rootView.parent != null) {
                contentParent.removeView(currentViewHolder!!.rootView)
                addView(viewType)
            }
        }
    }

    private fun addView(viewType: ViewType) {
        val viewHolder = getViewHolder(viewType)
        val rootView = viewHolder.rootView
        if (rootView.parent != null) {
            (rootView.parent as ViewGroup).removeView(rootView)
        }
        if (viewType == ViewType.CONTENT) {
            //为了解决Fragment中content布局无法撑满全屏
            contentParent.addView(
                rootView,
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        } else {
            contentParent.addView(rootView)
        }
        currentViewHolder = viewHolder
    }

    private fun notifyDataSetChanged(adapter: Adapter<ViewHolder>) {
        getViewType(adapter)?.apply {
            adapter.onBindViewHolder(getViewHolder(this))
        } ?: run {
            LogUtils.dTag(DEBUG_TAG, "notifyDataSetChanged  getViewType(adapter) == null")
        }
    }

    private fun getViewHolder(viewType: ViewType): ViewHolder {
        if (viewHolders[viewType] == null) {
            addViewHolder(viewType)
        }
        return viewHolders[viewType] as ViewHolder
    }

    private fun getViewType(targetAdapter: Adapter<*>): ViewType? {
        for (entry in adapters.entries) {
            if (entry.value == targetAdapter) {
                return entry.key as ViewType
            }
        }
        return null
    }

    /**
     * 新增错误文案处理,如果当前展示的是[ViewType.ERROR]布局,则直接显示在布局上,反之则使用toast
     *
     * @param str 错误文案
     * @param id 展示错误文案的布局id,一般是[ViewType.ERROR]布局中的id
     */
    open fun setErrorAdapterMsg(str: String, @IdRes id: Int = R.id.error_msg) {
        currentViewHolder?.let {
            if (it.viewType == ViewType.ERROR) {
                val errorMsgTv: TextView = it.rootView.findViewById(id)
                errorMsgTv.text = str
            } else {
                it.rootView.context.toast(str)
            }
        }
    }

    @Suppress("UNCHECKED_CAST")
    fun <T : Adapter<out ViewHolder>> getAdapter(viewType: ViewType) = adapters[viewType] as T

    private fun addViewHolder(viewType: ViewType) {
        val adapter: Adapter<ViewHolder> = getAdapter(viewType)
        val viewHolder = if (adapter is ContentAdapter<*>) {
            adapter.onCreateViewHolder(contentView)
        } else {
            adapter.onCreateViewHolder(LayoutInflater.from(contentParent.context), contentParent)
        }
        viewHolder.viewType = viewType
        viewHolder.onReloadListener = onReloadListener
        viewHolders[viewType] = viewHolder
        adapter.onBindViewHolder(viewHolder)
        adapter.listener = this::notifyDataSetChanged
    }

    abstract class Adapter<VH : ViewHolder> {
        internal lateinit var listener: (adapter: Adapter<ViewHolder>) -> Unit

        abstract fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): VH

        abstract fun onBindViewHolder(holder: VH)

        @Suppress("UNCHECKED_CAST")
        fun notifyDataSetChanged() = listener.invoke(this as Adapter<ViewHolder>)
    }

    abstract class ContentAdapter<VH : ViewHolder> : Adapter<VH>() {
        override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup) =
            onCreateViewHolder(View(parent.context))

        abstract fun onCreateViewHolder(contentView: View): VH
    }

    private class SimpleContentAdapter : ContentAdapter<ViewHolder>() {
        override fun onCreateViewHolder(contentView: View): ViewHolder =
            ViewHolder(
                contentView
            )

        override fun onBindViewHolder(holder: ViewHolder) = Unit
    }

    open class ViewHolder(val rootView: View) {
        internal var viewType: ViewType? = null
        var onReloadListener: OnReloadListener? = null
            internal set
    }

    abstract class DecorAdapter {
        abstract fun onCreateDecorView(inflater: LayoutInflater): View

        abstract fun getContentParent(decorView: View): ViewGroup
    }

    private class LinearDecorAdapter(private val views: List<View>) : DecorAdapter() {
        override fun onCreateDecorView(inflater: LayoutInflater) =
            LinearLayout(inflater.context).apply {
                orientation = LinearLayout.VERTICAL
                for (view in views) {
                    addView(view)
                }
            }

        override fun getContentParent(decorView: View) = decorView as ViewGroup
    }

    class AdapterPool internal constructor(private val helper: LoadingHelper) {
        fun register(viewType: ViewType, adapter: Adapter<*>) {
            helper.register(viewType, adapter)
        }
    }

    interface OnReloadListener {
        fun onReload()
    }
}

enum class ViewType(val state: String) {
    TITLE("title"),
    LOADING("loading"),
    CONTENT("content"),
    ERROR("error"),
    EMPTY("empty")
}